import { Inject, Injectable } from '../decorators/inject';
import { NExcel } from '../types';
import { fromEvent, Observable, Subject } from 'rxjs';
import { map, throttleTime } from 'rxjs/operators';
import { Config } from './config';
import { NTable } from '../types/table.interface';
import { TableDatas } from './table-datas';
import { getPixelRatio } from '../utils/tools';
import { Hook } from './hook';
import { Cache } from './cache';
import { MergeCell } from './merge-cell';
import { ICell } from '../types/z-excel.interface';
import { equalMerge } from '../utils/tools';
import { NTableEvent } from '../types/cors';
// import { CellRenderEditManage } from './cell-render-edit-manage';
type renderEvent = 'afterRender' | 'afterBackgroundRender';

@Injectable('TableEvent')
export class TableEvent implements NTable.IInit, NTable.ITriggerRender, NTableEvent.IDataEvent, NTableEvent.IMouseEvent, NTableEvent.IGenerateEvent {
    public scrollObs$: Observable<{ height: number, directive: NTableEvent.TScrollDirective, width: number }>;
    public render$: Subject<{ type: renderEvent }> = new Subject();
    protected clickInterval = 300;
    public triggerRender$: Subject<any> = new Subject();
    // public doubleClick$: Observable<TMergeCell&IPositionDetailInfo> = new Subject();
    // public click$: Observable<any> = new Subject();
    public contextMenu$: Observable<{x: number, y: number}> = new Subject();
    public customEvent$: Observable<{type: string, payload: any}> = new Subject();
    public tableResize$: Observable<{type: string, payload: any}> = new Subject();
    public tableCellValueChange: Subject<{row: number, col: number}> = new Subject();
    public tableRowValueChange: Subject<{row: number}> = new Subject();
    public tableColValueChange: Subject<{col: number}> = new Subject();
    public tableValueRefresh: Subject<{col: number}> = new Subject();
    public clearEmptyTableData: Subject<{row:number,col: number}> = new Subject();
    constructor(
        @Inject(NExcel.OperateContainerToken) protected ele: HTMLElement,
        protected conf: Config,
        protected datas: TableDatas,
        @Inject(NExcel.CanvasToken) protected canvasEle: HTMLCanvasElement,
        @Inject(NExcel.TableDataCanvasFillToken) protected fillCanvas: HTMLCanvasElement,
        @Inject(NExcel.TableDataCanvasToken) protected dataCanvas: HTMLCanvasElement,
        @Inject(NExcel.TableContainerEleToken) protected zExcelContainer: HTMLElement,
        @Inject(NExcel.TableFreezeCanvasToken) protected freezeContainer: HTMLCanvasElement,
        protected hook: Hook,
        protected cache: Cache,
        protected mergeCell: MergeCell
    ) {

    }
    public init() {
        this.scrollObs$ = new Subject();
        fromEvent<any>(this.ele, 'mousewheel')
            .pipe(map((evt:WheelEvent) => {
                // @ts-ignore
                evt.stopPropagation();
                evt.preventDefault();
                return evt;
            }))
            // .pipe(throttleTime(__ENV__ === 'development' ? 150 : 25))
            // .pipe(throttleTime(__ENV__ === 'development' ? 100 : 100))
            .pipe(map((evt: WheelEvent) => {
                // 垂直滚动
                if(Math.abs(evt.deltaY) > Math.abs(evt.deltaX)) {
                    let heightTotal = 0;
                    let maxBreakRow = -1;
                    let row = this.conf.freeze.row + 1;
                    while (true) {
                        if (maxBreakRow > this.conf.allowMaxRender.row) {
                            console.log('maxBreakRow break!');
                            break;
                        } else {
                            maxBreakRow++;
                        }
                        let start = { y: heightTotal + this.datas.getRowHeight(row)};
                        if (start.y > this.conf.view.row) {
                            if (evt.deltaY > 0) {
                                return { height: start.y, directive: 'bottom', event: evt};
                            } else {
                                let val = heightTotal - this.datas.getRowHeight(row - 1);
                                return { height: val > 0 ? val : 0, directive: 'top', event: evt};
                            }
                        } else {
                            heightTotal += this.datas.getRowHeight(row);
                            row++;
                        }
                    }
                } else {
                    // 水平滚动
                    let widthTotal = 0;
                    let maxBreakCol = -1;
                    let col = this.conf.freeze.col + 1;
                    while(true) {
                        if(maxBreakCol > this.conf.allowMaxRender.col)  {
                            console.error('maxBreakCol break!');
                            break;
                        } else {
                            maxBreakCol++;
                        }
                        let start = {x: widthTotal + this.datas.getColWidth(col)};
                        if(start.x > this.conf.view.col) {
                            if(evt.deltaX > 0) {
                                return {width: start.x, directive: 'right', event: evt};
                            } else {
                                let val = widthTotal - this.datas.getColWidth(col - 1);
                                return {width: val > 0 ? val : 0, directive: 'left', event: evt};
                            }
                        } else {
                            widthTotal += this.datas.getColWidth(col);
                            col++;
                        }
                    }
                }
            })).subscribe(res => {
                // console.log(res);
                // @ts-ignore
                this.scrollObs$.next(res);
            });
        
        this.initClick();
        this.triggerRender$.subscribe(res => {
            this.triggerRender();
        });
        this.ele.addEventListener('contextmenu', (e) => {
            console.log(e);
            e.stopPropagation();
            e.preventDefault();
            // @ts-ignore
            this.contextMenu$.next({
                x: e.offsetX,
                y: e.offsetY,
                originEvent: e
            });
        });
        let timmer: any = null;
        window.addEventListener('resize', () => {
            if(timmer !== null) {
                clearTimeout(timmer);
            }
            timmer = setTimeout(() => {
                const width = this.zExcelContainer.clientWidth;
                const height = this.zExcelContainer.clientHeight;
                let pixelRatio = getPixelRatio(this.canvasEle.getContext('2d'));
                this.hook.pixelRatioChange.excute(pixelRatio);
                this.conf.pixelRatio = pixelRatio;
                this.resetCanvasSize(width, height);
                // @ts-ignore
                this.tableResize$.next({width: width, height: height});
            });
        });
        const width = this.zExcelContainer.clientWidth;
        const height = this.zExcelContainer.clientHeight;
        this.conf.pixelRatio = getPixelRatio(this.canvasEle.getContext('2d'));
        this.resetCanvasSize(width, height);
        // @ts-ignore
        this.tableResize$.next({width: width, height: height});
        this.initMouseover();
        this.initMouseout();
        this.tableCellValueChange.subscribe(res => {
            this.tableRowValueChange.next({row: res.row});
            this.tableColValueChange.next({col: res.col});
        });
        this.clearEmptyTableData.subscribe(({row,col}) => {
            this.datas.clearData(row, col);
        });
    }
    protected resetCanvasSize(width: number, height: number) {
        [this.canvasEle, this.dataCanvas, this.fillCanvas, this.freezeContainer].forEach(ele => {
            ele.setAttribute('style', `width: ${width}px; height: ${height}px`);
            let ratio = getPixelRatio(ele.getContext('2d'));
            ele.height = height * ratio;
            ele.width = width * ratio;
        })
    }
    protected createEventValue<T extends ExcelEventType>(event: MouseEvent, type: T): ExcelEvent<T> {
        let evtX = event.offsetX;
        let evtY = event.offsetY;
        let x = evtX * this.conf.enlarge;
        let y = evtY * this.conf.enlarge;
        let col = this.cache.getPointCol(evtX);
        let row = this.cache.getPointRow(evtY);
        let position = this.mergeCell.getMerge(row, col);
        let mergeCell = {row: row, col: col, rowspan: 1, colspan: 1};
        if(position !== null) {
            mergeCell = position;
        }
        let eventValue: ExcelEvent<T> =  {
            buttonType: event.button,
            type: type,
            evtX: evtX,
            evtY: evtY,
            x: x,
            y: y,
            ...mergeCell,
            cell: {}
        }
        return eventValue;
    }
    public click$: Observable<ExcelClickEvent> = new Subject();
    public dbclick$: Observable<ExcelDbclickEvent> = new Subject();
    public mousedown$: Observable<ExcelMousedownEvent> = new Subject();
    public mouseup$: Observable<ExcelMouseupEvent> = new Subject();
    protected initClick() {
        const buttonType = {
            0: 'left',
            1: 'middle',
            2: 'right'
        }
        let timmer: any = null;
        this.ele.addEventListener('click', (event) => {
            if(timmer ===  null) {
                timmer = setTimeout(() => {
                    clearTimeout(timmer);
                    timmer= null;
                    let eventValue = this.createEventValue(event, 'click');
                    let res = this.hook.clickEvent.excute(eventValue);
                    if(res) {
                        (this.click$ as Subject<ExcelClickEvent>).next(res[0]);
                    }
                }, this.clickInterval)
            } else {
                clearInterval(timmer);
                timmer = null;
                let eventValue = this.createEventValue(event, 'dbclick');
                let res = this.hook.dbclickEvent.excute(eventValue);
                if(res) {
                    (this.dbclick$ as Subject<ExcelDbclickEvent>).next(res[0]);
                }
            }
        });
        let isDown = false;
        this.ele.addEventListener('mousedown', (event) => {
            let eventValue = this.createEventValue(event, 'mousedown');
            let res = this.hook.mousedownEvent.excute(eventValue);
            if(res) {
                (this.mousedown$ as Subject<ExcelMousedownEvent>).next(res[0]);
                isDown = true;
            }
        });
        this.ele.addEventListener('mouseup', (event) => {
            let eventValue = this.createEventValue(event, 'mouseup');
            let res = this.hook.mousedownEvent.excute(eventValue);
            if(res) {
                (this.mouseup$ as Subject<ExcelMouseupEvent>).next(res[0]);
                isDown = false;
            }
        });
    }
    public hover$: Observable<ExcelHoverEvent> = new Subject();
    public mouseover$: Observable<ExcelMouseoverEvent> = new Subject();
    public mouseout$: Observable<ExcelMouseoutEvent> = new Subject();
    protected initMouseover() {
        let hoverPosition: ExcelEvent<'hover'> = null;
        fromEvent(this.ele, 'mousemove')
        .pipe(throttleTime(30))
        // @ts-ignore
        .subscribe((event: MouseEvent) => {
            let eventValue = this.createEventValue(event, 'hover');
            if(hoverPosition === null || !equalMerge(hoverPosition, eventValue)) {
                let _eventValue = this.createEventValue(event, 'hover');
                let res = this.hook.hoverEvent.excute(_eventValue);
                if(res) {
                    (this.hover$ as Subject<ExcelHoverEvent>).next(res[0]);
                    hoverPosition = res[0];
                }
            } else {
                let _eventValue = this.createEventValue(event, 'mouseout');
                let res = this.hook.mouseoutEvent.excute(_eventValue);
                if(res) {
                    (this.mouseout$ as Subject<ExcelMouseoutEvent>).next(res[0]);
                }
            }
            let _eventValue = this.createEventValue(event, 'mouseonver');
            let res = this.hook.mouseoverEvent.excute(_eventValue);
            if(res) {
                (this.mouseover$ as Subject<ExcelMouseoverEvent>).next(res[0]);
            }
        });
        fromEvent(this.ele, 'mouseout').subscribe(res => {
            if(hoverPosition !== null) {
                let res = this.hook.mouseoutEvent.excute({...hoverPosition, type: 'mouseout'});
                if(res) {
                    (this.mouseout$ as Subject<ExcelMouseoutEvent>).next(res[0]);
                    hoverPosition = null;
                }
            }
        });
    }
    protected initMouseout() {

    }
    public triggerRender() {

    }
}